package joc.internal.transformer.helper;

import javassist.CannotCompileException;
import javassist.CtBehavior;
import javassist.expr.ExprEditor;
import javassist.expr.FieldAccess;
import javassist.expr.MethodCall;
import joc.JOCEnabled;
import joc.contract.Pure;
import joc.internal.EvaluationResult;
import joc.internal.Evaluator;
import joc.internal.compiler.BooleanExp;
import joc.internal.compiler.ConstructorExp;
import joc.internal.compiler.NestedExp;
import joc.internal.compiler.StandaloneExp;
import joc.internal.compiler.StaticCallExp;
import joc.internal.compiler.ValueExp;
import joc.internal.transformer.ClassTransformer;

public class PureExpressionEditor extends ExprEditor {
	private final ClassTransformer transformer;
	private final boolean allowOwnStateChange;
	private boolean hasError = false;

	public PureExpressionEditor(ClassTransformer transformer, boolean allowOwnStateChange) {
		this.transformer = transformer;
		this.allowOwnStateChange = allowOwnStateChange;
	}

	@Override
	public void edit(FieldAccess fieldAccess) {
		try {
			if (fieldAccess.isWriter()
					&& (!allowOwnStateChange || !fieldAccess.getField().getDeclaringClass()
							.equals(fieldAccess.where().getDeclaringClass()))) {
				String errorMessage = "Illegal write access in method " + fieldAccess.where().getLongName()
						+ " on field " + fieldAccess.getClassName() + "." + fieldAccess.getFieldName() + ".";
				insertError(errorMessage, fieldAccess.where());
			}
		} catch (Exception e) {
			transformer.getLogger()
					.error("Unknown exception while parsing method calls of method "
							+ fieldAccess.where().getLongName() + ".", e);
		}
	}

	@Override
	public void edit(MethodCall methodCall) {
		try {
			if (transformer.isSynthetic(methodCall.where())) {
				transformer.getLogger().warn(methodCall.where().getLongName() + " is synthetic");
			}
			if (transformer.isSynthetic(methodCall.getMethod())) {
				transformer.getLogger().warn(
						methodCall.getMethod().getLongName() + " being called from " + methodCall.where().getLongName()
								+ " is synthetic");
			}
			if (methodCall.getMethod().getDeclaringClass().hasAnnotation(JOCEnabled.class)
					&& !transformer.hasInheritedAnnotation(methodCall.getMethod(), Pure.class)
					&& !transformer.isSynthetic(methodCall.where()) && !transformer.isSynthetic(methodCall.getMethod())) {
				String errorMessage = "Illegal method access in method " + methodCall.where().getLongName()
						+ " on method " + methodCall.getMethod().getLongName() + ".";
				insertError(errorMessage, methodCall.where());
			}
		} catch (Exception e) {
			transformer.getLogger().error(
					"Unknown exception while parsing method calls of method " + methodCall.where().getLongName() + ".",
					e);
		}
	}

	private void insertError(String errorMessage, CtBehavior behavior) throws CannotCompileException {
		transformer.getLogger().error(errorMessage);
		if (!hasError) {
			NestedExp evaluationResult = new ConstructorExp(EvaluationResult.class, new ValueExp(
					transformer.getRecipientString(behavior)), new ValueExp(Pure.class), BooleanExp.FALSE,
					new ValueExp(errorMessage));
			StandaloneExp exp = new StaticCallExp(Evaluator.evaluateContract, evaluationResult).toStandalone();
			exp.insertBefore(behavior);
			hasError = true;
		}
	}
}